package org.hagopa.games.moneypool.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.hagopa.games.moneypool.Configuration;
import org.hagopa.games.moneypool.ServerVersion;
import org.hagopa.games.moneypool.dblayer.Connector.DBConnector;
import org.hagopa.games.moneypool.msgs.CommandsEnum;
import org.hagopa.games.moneypool.msgs.Message;
import org.hagopa.games.moneypool.msgs.ContentTypes.service.PlayerType;
import org.hagopa.games.moneypool.msgs.ContentTypes.service.Version;

public class Server implements Runnable {
	Selector sel = null;
	private ServerSocket srvSocket = null;
	private int port = 3456;
	private static ServerSocketChannel ssc = null;
	private ConcurrentLinkedQueue <NetClientHandler> waitReception = null;
	//private Reception waitReception = null;
	private static int clid = 1;
        private final static String hashKey="tp1igg2igf";
	private List<NetClientHandler> tempClients = new ArrayList<NetClientHandler>();
	
    private boolean isRunning = false;
	
	
	
	public Server(int port)
	{
		this.port = port;
		//new Thread(waitReception).start();
		System.out.println("[Server]Server Created !!!");
	}
	
	
	public boolean init(ConcurrentLinkedQueue <NetClientHandler> rec)
	{
		this.waitReception = rec;
		this.isRunning = true;
		
		DBConnector nDB = DBConnector.getInstance();
		DBConnector.setInitialSettings(Configuration.getDBHost(),Configuration.getUser(), 
											Configuration.getPwd(), Configuration.getDbname());
		if(null == nDB)
		{
			System.out.println("[Server](inti) - Failed to create DB handler!");
			return false;
		}
		DBConnector.connect();
		if(DBConnector.isConnected()==false)
			return false;
		new Thread( this ).start();
			return true;
	}
	private long createSessionId()
	{
		int sessId = 12;

		Date dt = new Date();
		return (sessId * dt.getTime()); 
	}
	private void sendDeny(NetClientHandler client)
	{
		System.out.println("[Server](sendDeny) - Create and send deny msg");
		Message reply = new Message();
                reply.setClientIP(client.getClientIP());
                reply.setHashKey(Server.hashKey);
		reply.setClientId(client.getClientId());
		reply.setSessionId(client.getSessionId());
		reply.setCommand(CommandsEnum.DENY);
		PlayerType pt = new PlayerType();
		pt.setType(1);
		pt.setPlayerId(1);
		reply.setContentSz(8);
		reply.setContentHandler(pt);
		client.setStatus(ClientStatus.DISCONNECTED);
		client.sendMsg(reply.marshall());
		
		
	}
	private Message serv_helo(SocketChannel socketChannel) throws IOException
	{
		Message reply = new Message();
		reply.setClientId(clid);
		clid++;
                
		reply.setClientIP(socketChannel.socket().getInetAddress().getHostAddress()); //.getCanonicalHostName());
                reply.setHashKey(Server.hashKey);
		reply.setSessionId(createSessionId());
		reply.setCommand(CommandsEnum.SERV_HELLO);
		reply.setContentSz(8);
		
		Version vers = new Version();
		vers.setMajor(ServerVersion.getMain());
		vers.setSubVer(ServerVersion.getMajor());
		
		reply.setContentHandler(vers);
		
		// Accept the connection and make it non-blocking
		
		//Socket socket = socketChannel.socket();
		socketChannel.configureBlocking(false);
		InetAddress ipa = socketChannel.socket().getInetAddress();
		System.out.println("[Server](accept) - IPAddress : " + ipa.getHostName());
		byte[] msg = null;
		msg = reply.marshall();
	
		ByteBuffer src = ByteBuffer.wrap(msg);
		
		socketChannel.write(src);
		System.out.println("[Server](accept) - Sent Message");
		return reply;
	}
	private synchronized  SocketChannel accept(SelectionKey key) throws IOException {
		// For an accept to be pending the channel must be a server socket channel.
		ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
		System.out.println("\n=================\n[Server](accept) - Accept New Connection!!\n=================");
		SocketChannel socketChannel = serverSocketChannel.accept();
		socketChannel.configureBlocking(false);
		serv_helo(socketChannel);
		socketChannel.register(this.sel, SelectionKey.OP_WRITE);
		socketChannel.register(this.sel, SelectionKey.OP_READ);
		
		return socketChannel;
	}
	
	private boolean startServer()
	{
		System.out.println("\n=================\n[Server](startServer) - Starting Server!!!!!\n=================\n");
		ServerVersion.Print();
		try {
			// Instead of creating a ServerSocket,
		      // create a ServerSocketChannel
			ssc = ServerSocketChannel.open();

		      // Set it to non-blocking, so we can use select
			ssc.configureBlocking( false );

		      // Get the Socket connected to this channel, and bind it
		      // to the listening port
		    srvSocket = ssc.socket();
		      
		    if(null == srvSocket)
			{
		    	System.out.println("[Server](startServer) - Failed to create Server Socket");
				return false;
			}
		      
		    InetSocketAddress isa = new InetSocketAddress( port );
		    srvSocket.bind( isa );

			sel = Selector.open();
		
			ssc.register(sel, SelectionKey.OP_ACCEPT);
		}
		catch (ClosedChannelException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} 
		
		
		return true;
	}
	
	
	public void run() 
	{
		Thread.currentThread().setName("BogoGameServerThread");
		if(startServer() == true)
		{
			System.out.println("\n-----------------\n[Server](run) - Started succesfully\n--------------------\n");
			while(isRunning)
			{
				int num = 0;
				try {
					num = sel.select();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if(num == 0)
					continue;
				
				Set<SelectionKey> keys = Collections.synchronizedSet(sel.selectedKeys());
				
				Iterator<SelectionKey> it = keys.iterator();
					while(it.hasNext())
					{
				
					SelectionKey skey = (SelectionKey)it.next();
					it.remove();
						if(skey.isValid() && skey.isAcceptable())
						{
							SocketChannel cs = null;
							try {
								cs = accept(skey);
								NetClientHandler client = new NetClientHandler(cs);
								
								if(null == client)
								{
									System.out.println("[Server](run) - No client created!!!");
									break;
								}
								else
								{
                                                                        client.setHashKey(Server.hashKey);
									client.setWhereRegistered("Server");
									tempClients.add(client);
								}

							} catch (IOException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							
						}
						else if (skey.isValid() && skey.isReadable())
						{
							System.out.println("\n------------------\n[Server](run) -  Readable!!!\n------------------------\n");
							isReadableEvent(skey);
						}
					}
				keys.clear();
				//cleanUpReception();
			}
		
			
		}
	
	}
	
//	public void cleanUpReception()
//	{
//		Iterator <NetClientHandler> it = this.waitReception.getIter();
//		while(it.hasNext())
//		{
//			NetClientHandler tmp = it.next();
//			if(tmp.getChannel().isConnected() == false)
//				it.remove();
//		}
//	}
//	
	public void isReadableEvent(SelectionKey skey)
	{
		Iterator<NetClientHandler> iter =  tempClients.iterator();
		SelectableChannel channel = skey.channel();
		while(iter.hasNext())
		{
			NetClientHandler tmp = (NetClientHandler)iter.next();
			System.out.println("[Server](isReadableEvent) - Found Channel");
			if(channel.equals(tmp.getChannel()))
			{
				if(null == tmp.getMsg())
				{
					System.out.println("[Server](isReadableEvent) - !!! Empty msg");
					removeChannel(skey);
					break;
				}
				if(tmp.getChannel().isConnected() == true)
				{
					System.out.println("[Server](isReadableEvent) - ClientId: " + tmp.getClientId());
					System.out.println("[Server](isReadableEvent) - GameId:"  + tmp.getGameId());
					if(tmp.getLastClientMsg().getCommand().equals(CommandsEnum.INTRO))
					{
						iter.remove();
						skey.cancel();
						System.out.println("[Server](isReadableEvent) - before appending to waitReception");
						if(validateClient(tmp))
						{
							if(!waitReception.add(tmp))
								System.out.println("[Server](isReadableEvent)  -Failed to append element to Q");
						}
						else
						{
							sendDeny(tmp);
						}
						tmp.setWhereRegistered("none");
						System.out.println("[Server](isReadableEvent) -- Add Client. Now in Reception: " + waitReception.size() + " clients in Reception");
						
					}
					
				}
				else
				{
					skey.cancel();
				}
			} 
		}
	}
	private boolean validateClient(NetClientHandler clnt)
	{
		// check connected client for valid Id, valid game id and so on
		if(loadData(clnt.getClientId(),clnt.getGameId()) == false)
			return false;
		return true;
	}
	
	public boolean loadData(int cid, int gid) {
		System.out.println("[Server](loadData) -  Loading User Data...");
		String sqlrequest = "select * from user_games where user_id=" + cid + " AND game_id=" + gid +";";
		Statement stmt = null;
		ResultSet rs = null;
		try {
			stmt = DBConnector.getConn().createStatement();
						
			rs = stmt.executeQuery(sqlrequest);
		
			while(rs.next())
			{
				//Retrieve by column name
				int uid = rs.getInt("user_id");
				System.out.println("[Server](loadData) - USER ID:" + uid);
				Date regDate = rs.getDate("reg_date");
				System.out.println("[Server](loadData) - Registred : " + regDate);	
			}

			rs.close();
			stmt.close();
		
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("[Server](loadData) - Failed TO GET DATA FROM DB");
			return false;
		}
		return true;
	}
	
	private void removeChannel(SelectionKey key)
	{
		
		key.cancel();
		try {
			key.channel().close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void stopServer()
	{
		this.isRunning = false;
	}
}
